iT邦幫忙

2022 iThome 鐵人賽

DAY 12
3
Software Development

30 天與九頭蛇先生做好朋友系列 第 12

實作 Consent Provider

  • 分享至 

  • xImage
  •  

昨天成功地完成了身分驗證,於是 IdP 系統已經知道使用者是誰了--也就是 subject 的值。下一步要來問使用者,是否要同意(Consent)應用程式的授權請求。

在昨天最後一步的程式如下:

目前的程式碼太亂,所以有做了一次重構,可以參考 Commit

return Redirect::away($completedRequest->getRedirectTo());

從 Hydra 回應的 CompletedRequest 物件裡,有個欄位是 redirect_to,這會讓你知道接下來要帶什麼參數回去 Hydra。

回到 Hydra 後,跟身分驗證很像,Hydra 會檢查使用者過去是否有授權過。如果使用者沒有授權過的記錄,或是應用程式要求了以前沒授權過的範圍,這時就輪到 Consent Provider 出馬來跟使用者互動了。

處理同意請求

同意請求為 Consent Request 的直譯。

過程與 Login Provider 類似,首先會被轉導至 Consent Provider。設定一樣是存在設定檔裡,欄位是 urls.consent ,下面是設定:

urls:
  consent: http://127.0.0.1:8000/oauth2/consent

對應的導頁網址範例如下:

http://127.0.0.1:8000/oauth2/consent?consent_challenge=5678

與登入流程相同,無論之前是否有授權,都一定會轉導至 Consent Provider,讓開發者能夠在這個節點做對應的處理。

Consent Provider 在處理 request 的時候,首先要使用 consent_challenge 跟 Hydra API 取得資訊。

下面是 Laravel + Hydra SDK 的程式碼範例:

public function __invoke(Request $request, AdminApi $adminApi)
{
    $consentChallenge = $request->input('consent_challenge');

    $consentRequest = $adminApi->getConsentRequest($consentChallenge);

    Log::debug('Get consent Request', json_decode((string)$consentRequest, true));
}

透過 Log 可以看得到 $consentRequest 的資訊如下:

{
  "acr": "",
  "amr": [],
  "challenge": "1982b3eb2c774c5b93c667d69719dbef",
  "client": {"client_id": "my-rp", "...": "..."},
  "context": [],
  "login_challenge": "38edf5462fde46e690ccde80a1b97b66",
  "login_session_id": "b8a40193-dd7b-44dc-a019-ba14a57e7531",
  "oidc_context": [],
  "request_url": "http://127.0.0.1:4444/oauth2/auth?client_id=my-rp&...",
  "requested_access_token_audience": [],
  "requested_scope": [
    "openid"
  ],
  "skip": false,
  "subject": "1"
}

與 Login Provider 相同,先來看 skipsubject

skip 代表的使用者在這個裝置上是否曾同意授權過。如果 skipfalse 的話,需要顯示授權頁,並使用 requested_scope 的資訊顯示使用者需要同意授權的範圍。如果應用程式是第一方的話,有些情境下可能會跳過這一步--因為這個應用程式跟認證系統是同個組織所開發的。如果 skiptrue 的話,則不應該顯示使用者介面,而是要接受(或拒絕)同意請求。通常應該都要接受,除非有其他充分的理由才會是由系統自動拒絕請求。

其他欄位都跟登入請求相同,像 clientlogin_session_id(登入請求的 session_id)、oidc_contextrequest_urlrequested_access_token_audience 都是相同的,這些就不作說明

額外多出來的幾個欄位說明如下:

acrOpenID Connect Core 1.0 - 2. ID Token 的定義,全名為 Authentication Context Class Reference,直譯為「身分驗證上下文類別參考」。它指的是 IdP 在什麼樣的環境下來執行身分驗證,這個內容可以參考 OpenID Connect Extended Authentication Profile (EAP) ACR Values 1.0 協定,雖然還在草稿階段,但設計的方法是可以看一下的,像裡面就有提到 phr 指的是防釣魚(Phishing-Resistant)機制。

amrOpenID Connect Core 1.0 - 2. ID Token 的定義,全名為 Authentication Methods References,直譯為「身分驗證方法參考」。它代表了身分驗證系統如何確認使用者身分的,比方說透過帳號密碼與 OTP 驗證方法,因為有可能會有多個驗證因子(Multi-factor authentication),因此它的格式是 Array,應用程式可以參考這個值來了解 IdP 是如何做身分驗證的。

以 Hydra 的情境來說,acramr 都是由 Login Provider 處理完身分驗證後再決定內容,並記錄在 AcceptLoginRequest 物件裡傳回 Hydra,只是因為協定定義這個欄位是非必要,所以昨天沒有額外處理這件事。

context 是昨天賣的關子的說明,昨天在 Login Provider 傳入的 context,會在今天的 Consent Provider 拿到相同的內容。可能有人會想說,用 Session 或 Cookie 就能輕鬆辦到的事,為什麼要用這個方法。原因很簡單:考慮到 Login Provider 與 Consent Provider 是不同服務的時候(Hydra 的設計是考慮到各個 Provider 都是獨立的微服務),網域或背後所能控制的資訊就有可能會有所不同,因此用 context 傳輸特定的內容反而是會很好控制的。

有了欄位資訊後,接下來就能呈現頁面了。這裡就不獻截圖了,由讀者自由想像,文末會附原始碼參考。

接受或拒絕同意請求

跟身分驗證的情境是不同的,使用者可能會接受,也有可能會拒絕應用程式的授權請求,因此這裡就要明確做一個拒絕授權請求的流程。

假設使用者接受同意請求,則跟登入流程類似:

$acceptConsentRequest = new AcceptConsentRequest([
    'grantScope' => ['oidc'],
    'remember' => false,
    'rememberFor' => 0,
    'session' => [
        'access_token' => [],
        'id_token' => [],
    ],
]);

$completedRequest = $adminApi->acceptConsentRequest($consentChallenge, $acceptConsentRequest);

return Redirect::away($completedRequest->getRedirectTo());

各個欄位的說明如下:

欄位 說明
grant_scope 使用者同意的範圍,通常都跟應用程式一樣,或是比應用程式要求的還要少。
remember 與 Login Provider 說明相同,這裡會對應到的是 consent_challenge 所取到的 skip,而不是 login_challenge 裡的 skip
remember_for 與 Login Provider 說明相同
session 這裡所設定的資訊,將會與之後發給應用程式的Access Token 或 ID Token 綁定。若是設定到 Access Token 的話,會在執行 Introspection 的時候看到設定的內容;若是設定 ID Token 的話,則是會成為 Claim 的內容。

相對地,拒絕同意請求與拒絕登入請求類似,使用相同的 RejectRequest,但呼叫不同 API 來完成:

$rejectRequest = new RejectRequest([
    'error' => 'access_denied',
    'errorDescription' => 'The request was rejected by end-user',
]);

$completedRequest = $adminApi->rejectConsentRequest($consentChallenge, $rejectRequest);

return Redirect::away($completedRequest->getRedirectTo());

欄位定義與重送攻擊的說明與 Login Provider 完全相同。

程式完成後,可以再執行一次登入。最後在所有任務都完成之後,就會回到註冊時定義的 callback,目前範例程式只有顯示「拿到身分驗證回應了」幾個字。然後注意網址列要像下面一樣:

http://127.0.0.1:8000/callback?code=Bx88NgWkNCqaThTnzLUkFOy-hPp4vO0F064XdbjtE6w.xFDHdkj14pKvR-kVFgzdeXVmRpF9NifskrcFmmYftoQ&scope=openid&state=1a2b3c4d

關鍵是要有 code 的欄位,看到這個欄位,Hydra 的任務就算完成了。再來就是要做最後的應用程式認證了。

程式碼調整可以參考 GitHub Commit。若執行過程有遇到奇怪問題的話,建議可以把所有服務和資料庫全部移除,然後再重頭建立一次,應該就能執行了。


上一篇
實作 Login Provider
下一篇
應用程式處理身分驗證回應
系列文
30 天與九頭蛇先生做好朋友23
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言